【Window】创建线程的3种方式 您所在的位置:网站首页 win32 线程分离 【Window】创建线程的3种方式

【Window】创建线程的3种方式

2024-07-17 06:54| 来源: 网络整理| 查看: 265

第一节:【Window】创建线程的3种方式 第二节:【Window】线程同步概述 第三节:【Window】线程同步方式1——临界区(关键代码段) 第四节:【Window】线程同步方式2——互斥量 第五节:【Window】线程同步方式3——事件 第六节:【Window】线程同步方式4——信号量

在这里插入图片描述

文章目录 一、 线程的状态二、创建线程2.1 创建线程方式1——CreateThread2.1.1 说明2.1.2 函数说明2.1.3 线程状态切换1. 启动线程2. 挂起线程3. 停止线程4. 等待状态 2.1.4 简单的例程 2.2 创建线程方式2——_beginthread2.2.1 函数说明2.2.2 安全属性2.2.3 线程终止2.2.4 _beginthread()和_beginthreadex()的区别2.2.5 实例 2.3 创建线程方式3——AfxBeginThread2.3.1 用户界面线程原型2.3.2 工作者线程原型2.3.3 结束线程2.3.4 举例 2.4 三者区别

一、 线程的状态

线程有挂起状态、执行状态、阻塞状态和等待状态。

下面分别介绍:

挂起状态:线程创建后并没有直接执行或是调用函数挂起了线程。被挂起了的线程没有执行的能力,只有调用启动函数了之后才能执行。执行状态:在线程的时间片内,拥有CPU资源的时候,这是,线程便开始执行。阻塞状态:由于进行大量输入输出操作或发生执行错误时,线程失去执行状态,只有等待问题解除之后,线程才能进入等待状态。等待状态:线程启动或时间片抢占失败是等待其他线程执行,在此期间,线程随时可能被执行。 二、创建线程 2.1 创建线程方式1——CreateThread 2.1.1 说明

线程内核对象就是一个包含了线程状态信息的数据结构。每次对CreateThread 的成功调用,系统都会在内部为其分配一个内核对象。

线程上下文反应了线程上次执行的寄存器状态,来保证线程之间切换(即还原现场)。

计数器,调用一次OpenThread(CreateThread ),计数器加1,CloseThread(CloseHandle)计数器减一。当计数器值为0时,没有线程使用该内核对象,系统收回内存。计数器的初始值是2(主线程是1,创建的线程是2)。

2.1.2 函数说明

头文件 因为C++不像Java一样需要进行跨平台优化,所以我们使用最简单的方法来实现多线程技术——windows.h中的CreateThread以及相关函数和类。首先,以如下的方式引用头文件:

#include

函数原型:

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全性描述(一个结构体,一般是NULL) SIZE_T dwStackSize, //一种数值(栈深度,一般是0) LPTHREAD_START_ROUTINE lpStartAddress, //启动函数 _In_opt_ __drv_aliasesMem LPVOID lpParameter, // 附加参数(一般为NULL) _In_ DWORD dwCreationFlags, //运行参数(是否在创建完成后就启动线程 _Out_opt_ LPDWORD lpThreadId // 返回句柄(一般是0,或者是一个DWORD型变量的地址,别忘了&) );

参数说明

lpThreadAttributes指向SECURITY_ATTRIBUTES结构的指针,决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。dwStackSize设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。pStartAddress指向线程函数的指针,函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。Parameter向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。CreationFlags控制线程创建的标志,可取值如下:1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程(就绪状态),直到线程被唤醒时才调用。2)0:表示创建后立即激活。3)STACK_SIZE_PARAM_IS_A_RESERVATION:dwStackSize参数指定初始的保留堆栈的大小,如果STACK_SIZE_PARAM_IS_A_RESERVATION标志未指定,dwStackSize将会设为系统预留的值lpThreadId保存新线程的id,是指向线程id的指针,如果为空,线程id不被返回

第三个参数 ——启动函数:

c LPTHREAD_START_ROUTINE lpStartAddress

我们一般这样写: (LPTHREAD_START_ROUTINE) ThreadStart 意思就是在线程启动的时候调用ThreadStart,之后他就不管了,也就是说这个函数? 就是线程主函数相当于main的意思。也就是说在这个函数中调用的类资源或函数资源 都是属于这个线程的。除了static的存储类。

倒数第二个参数——运行参数。

这是实际上是一个bool类型的值,用于标示是否在创建线程后立刻执行,如果为true,也就是0,那么就会立刻执行,否则将会挂起,等待启动

返回值 还有我要说一下HANDLE这个类型,它其实是一个指针,也是CreateThread的返回值。也就是一个线程句柄,用于标示一个线程。当然,其他对于线程的操作都需要使用这个指针。

函数成功,返回线程句柄,否则返回NULL。如果线程创建失败,可通过GetLastError函数获得错误信息。

注: 5.1 如果线程函数return,返回值会隐式调用ExitThread函数,可以使用GetExitCodeThread函数获得该线程函数的返回值。 5.2 使用CreateThread创建的线程具有THREAD_PRIORITY_NORMAL线程优先级。可以使用GetThreadPriority和SetThreadPriority函数获取和设置线程优先级值。 5.3 当一个线程结束时,这个线程的对象将获得有信号状态,使得任何等待这个对象的线程都能够成功并继续执行下去。 5.4 系统中的线程对象一直存活到线程结束,并且所有指向它的句柄都需要通过调用CloseHandle关闭。 5.5 如果一个线程调用了CRT,应该使用_beginthreadex 和_endthreadex(需要使用多线程版的CRT)。

2.1.3 线程状态切换 1. 启动线程

如果调用这个函数,将会启动HANDLE参数所代表的线程.

DWORD ResumeThread(HANDLE hThread); //启动线程 //说明:DWORD是一个数值,代表句柄,无需关注; //参数表示要启动的线程的句柄,也就是刚才介绍的由CreateThread返回的HANDLE 2. 挂起线程

下面我们看看如何挂起线程,使线程进入挂起状态:

DWORD SuspendThread(HANDLE hThread); //挂起线程 //说明:DWORD是一个整数值,代表一个句柄,无需过分关注 //参数:一个HANDLE线程指针,由CreateThread创建 3. 停止线程

挂起线程后可以进行释放以便停止线程:

delete HANDLE //释放指针资源 //说明:HANDLE是一个HANDLE型指针,代表释放一个线程的资源,使线程死亡

实际上,停止一个线程还有一种方法——强行停止,但是已经不建议使用,现在都是使用挂起+delete的方法,因为使用强行停止会有很多的安全问题,但也是一个功能,所以在这里为大家介绍一下:

BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode); //强行停止线程 //说明:返回值代表是否成功 //参数:HANDLE指针代表需要结束的线程,DWORD数值代表该线程的退出值 //功能:在任何位置结束任何线程 4. 等待状态

挂起线程可能为了等待重要操作然后再执行线程,以下函数将解除线程挂起状态,使线程进入等待状态:

DWORD ResumeThread(HANDLE hThread); //使线程脱离挂起状态 //说明:返回值也是句柄 //参数:HANDLE类型指针,表示要继续的线程,或刚创建而没有启动的线程 //注意:如果对等待状态下的线程使用本函数,可能会抛出异常或无效果,具体请见MSDN 2.1.4 简单的例程 //多线程抢占输出 #include #include using namespace std; void ThreadUser() { //线程入口 cout cout int id; //用于标识出票id int tickets; }_DATA, *_pDATA; DWORD WINAPI Fun1(LPVOID lpParam); DWORD WINAPI Fun2(LPVOID lpParam); void main() { HANDLE hThread1; HANDLE hThread2; _DATA stru_data; stru_data.id = 0; stru_data.tickets = 20; hThread1 = CreateThread(NULL, 0, Fun1, &stru_data, 0, NULL); hThread2 = CreateThread(NULL, 0, Fun2, &stru_data, 0, NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); } DWORD WINAPI Fun1(LPVOID lpParam) { _pDATA data = (_pDATA)lpParam; while (TRUE) { if (data->tickets > 0) { Sleep(1); cout if (data->tickets > 0) { Sleep(1); cout HANDLE hThread; unsigned int threadId; hThread = (HANDLE)_beginthreadex(NULL, NULL, ThreadProFunc, NULL, 0, &threadId); for (int i = 0; i for (int i = 0; i int id; //用于标识出票id int tickets; }_DATA, *_pDATA; //CRITICAL_SECTION g_cs; unsigned __stdcall Fun1(LPVOID lpParam); unsigned __stdcall Fun2(LPVOID lpParam); void main() { HANDLE hThread[2] = { NULL,NULL }; unsigned threadid[2] = { 0 }; _DATA stru_data; stru_data.id = 0; stru_data.tickets = 200; hThread[0] = (HANDLE)_beginthreadex(NULL, 0, Fun1, &stru_data, 0, &threadid[0]); hThread[1] = (HANDLE)_beginthreadex(NULL, 0, Fun2, &stru_data, 0, &threadid[1]); //InitializeCriticalSection(&g_cs); Sleep(4000); //LeaveCriticalSection(&g_cs); } unsigned __stdcall Fun1(LPVOID lpParam) { _pDATA data = (_pDATA)lpParam; while (TRUE) { //EnterCriticalSection(&g_cs); if (data->tickets > 0) { Sleep(1); cout _pDATA data = (_pDATA)lpParam; while (TRUE) { //EnterCriticalSection(&g_cs); if (data->tickets > 0) { Sleep(1); cout int i = 1; for (;;) i += i; return 0; } void CSingleThreadDlg::OnBnClickedStartthread() { //启动线程函数 mythread = AfxBeginThread( EndlessLoop,//即上面定义的函数 NULL ); } void CSingleThreadDlg::OnBnClickedPausethread() { // TODO: 在此添加控件通知处理程序代码 mythread->SuspendThread();//挂起线程 } void CSingleThreadDlg::OnBnClickedresumethread() { // TODO: 在此添加控件通知处理程序代码 mythread->ResumeThread();//恢复线程 }

2.4 三者区别 CreateThreadCreateThread是Windows API函数,提供操作系统级别操作,不用于MFC及RTL函数中。一般不建议调用此函数。CreateThread是Windows的API函数,提供操作系统级别的创建线程的操作。_beginthread(及_beginthreadex)与AfxBeginThread的底层实现都调用了CreateThread函数。_beginthread/beginthreadex_beginthread/beginthreadex函数在实现过程中都调用了CreateThread函数,但都在调用前做了很多初始化工作,在调用后又做了很多检查工作,这使得线程的创建更完整。结束线程的_endthread()函数和_endthreadex()函数在实现的过程中调用了Exithread()函数,但他们都做了更多的善后工作,其中的_endthread()函数甚至还包揽了句柄的删除工作。AfxBeginThreadAfxBeginThread是MFC创建线程函数,首先创建了相应的CWinThread对象,然后调用CWinThread::CreateThread,CWinThread::CreateThread中,完成了对线程对象的初始化工作。

CreateThread是由操作系统提供的接口,而AfxBeginThread和_BeginThread则是编译器对它的封装。

实际应用建议:

不要在一个MFC程序中使用_beginthreadex()或CreateThread()。这句话的意思是由于AfxBeginThread()是MFC封装的启动线程的函数,里面包含了很多和MFC相关的启动信息,而且封装了一些常用的操作,使用起来也比较简便。而用另外两个函数就需要程序员对类型,安全性检查进行更多的思考!

MFC中用_beginthreadex()函数应该是最佳选择,因为_beginthreadex()函数是CRun-timeLibrary中的函数,函数的参数和数据类型都是CRun-timeLibrary中的类型,这样在启动线程时就不需要进行Windows数据类型和CRun-timeLibrary中的数据类型之间的转化。减低了线程启动时的资源消耗和时间的消耗!

请牢记:MFC中,决不应该调用CreateThread。相反,应该使用Visual C++运行库函数_beginthreadex。

[c++11]多线程编程(一)——初识 [c++11]多线程编程(二)——理解线程类的构造函数

学习总结: xuanyin235 蒋鹿丸



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有